from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException, StaleElementReferenceException, \
    ElementClickInterceptedException
from longguo_ui.config.config import config
from longguo_ui.utils.logger import logger
from selenium.webdriver.support.ui import Select
from longguo_ui.utils.retry_utils import RetryUtils


class BasePage:
    """页面基础类，封装所有页面通用的操作方法"""

    def __init__(self, driver):
        """
        初始化页面基础类
        :param driver: 浏览器驱动实例
        """
        self.driver = driver  # 浏览器驱动对象
        self.wait = WebDriverWait(self.driver, config.EXPLICITLY_WAIT)  # 显式等待对象
        self.logger = logger  # 日志对象
        self.retry_utils = RetryUtils(max_retries=2, delay=0.5)  # 页面操作重试配置（最大重试2次，间隔0.5秒）

    # 查找单个元素
    def find_element(self, locator):
        """
        查找单个元素（显式等待元素存在于DOM中）
        :param locator: 元素定位器，格式为(By.xxx, "value")
        :return: 找到的元素对象
        :raises TimeoutException: 超时未找到元素时抛出
        :raises NoSuchElementException: 元素不存在时抛出
        """
        try:
            element = self.wait.until(EC.presence_of_element_located(locator))
            self.logger.info(f"找到元素: {locator}")
            return element
        except (TimeoutException, NoSuchElementException):
            self.logger.error(f"未找到元素: {locator}")
            raise

    # 查找多个元素
    def find_elements(self, locator):
        """
        查找多个元素（显式等待所有元素存在于DOM中）
        :param locator: 元素定位器，格式为(By.xxx, "value")
        :return: 找到的元素列表
        :raises TimeoutException: 超时未找到元素时抛出
        :raises NoSuchElementException: 元素不存在时抛出
        """
        try:
            elements = self.wait.until(EC.presence_of_all_elements_located(locator))
            self.logger.info(f"找到元素列表: {locator}")
            return elements
        except (TimeoutException, NoSuchElementException):
            self.logger.error(f"未找到元素列表: {locator}")
            raise

    # 点击元素（带重试）
    def click(self, locator):
        """
        点击元素（带重试机制），确保元素可点击后执行点击
        处理可能的点击失败情况：超时、元素过期、被遮挡
        :param locator: 元素定位器，格式为(By.xxx, "value")
        """

        def _click():
            """内部点击方法，供重试机制调用"""
            # 先确保元素可点击（可见且启用）
            element = self.wait.until(EC.element_to_be_clickable(locator))
            element.click()
            self.logger.info(f"点击元素: {locator}")

        # 针对点击可能出现的特殊异常添加重试
        self.retry_utils.execute_with_retry(
            _click,
            exceptions=(
                TimeoutException,  # 超时异常
                StaleElementReferenceException,  # 元素已失效（DOM已更新）
                ElementClickInterceptedException  # 被其他元素遮挡
            )
        )

    # 输入文本
    def send_keys(self, locator, text):
        """
        向元素输入文本（先清空再输入）
        :param locator: 元素定位器，格式为(By.xxx, "value")
        :param text: 要输入的文本内容
        :raises TimeoutException: 元素不可交互时抛出
        """
        try:
            element = self.wait.until(EC.element_to_be_clickable(locator))
            element.clear()  # 清空输入框
            element.send_keys(text)  # 输入文本
            self.logger.info(f"向元素 {locator} 输入文本: {text}")
        except TimeoutException:
            self.logger.error(f"无法向元素 {locator} 输入文本")
            raise

    # 获取元素文本
    def get_text(self, locator, max_retries=3):
        """
        获取元素文本（带重试机制，处理元素过期问题）
        :param locator: 元素定位器，格式为(By.xxx, "value")
        :param max_retries: 最大重试次数，默认3次
        :return: 元素的文本内容
        :raises StaleElementReferenceException: 多次重试后仍元素过期时抛出
        :raises TimeoutException: 元素超时未显示时抛出
        """
        for retry in range(max_retries):
            try:
                # 等待元素可见（不仅存在于DOM，还需可见）
                element = self.wait.until(EC.visibility_of_element_located(locator))
                return element.text
            except StaleElementReferenceException:
                if retry < max_retries - 1:
                    logger.warning(f"元素[{locator}]过期，重试第{retry + 1}次...")
                    continue  # 重试获取元素
                else:
                    logger.error(f"元素[{locator}]多次获取失败，已达最大重试次数")
                    raise
            except TimeoutException:
                logger.error(f"元素[{locator}]超时未出现")
                raise
        raise Exception(f"获取元素[{locator}]文本失败")

    # 判断元素是否显示
    def is_displayed(self, locator):
        """
        判断元素是否显示（可见）
        :param locator: 元素定位器，格式为(By.xxx, "value")
        :return: 布尔值，True表示元素显示，False表示元素未显示
        """
        try:
            displayed = self.find_element(locator).is_displayed()
            self.logger.info(f"元素 {locator} 显示状态: {displayed}")
            return displayed
        except:
            return False

    # 获取页面标题
    def get_title(self):
        """
        获取当前页面标题
        :return: 页面标题字符串
        """
        title = self.driver.title
        self.logger.info(f"当前页面标题: {title}")
        return title

    # 获取当前URL
    def get_current_url(self):
        """
        获取当前页面URL
        :return: 页面URL字符串
        """
        url = self.driver.current_url
        self.logger.info(f"当前页面URL: {url}")
        return url

    # 刷新页面
    def refresh(self):
        """刷新当前页面"""
        self.driver.refresh()
        self.logger.info("刷新页面")

    # 后退
    def back(self):
        """导航到浏览器历史记录中的上一页"""
        self.driver.back()
        self.logger.info("页面后退")

    # 前进
    def forward(self):
        """导航到浏览器历史记录中的下一页"""
        self.driver.forward()
        self.logger.info("页面前进")

    # 下拉框选择
    def select_option_by_value(self, locator, value):
        """
        通过value属性选择下拉框选项
        :param locator: 下拉框元素定位器，格式为(By.xxx, "value")
        :param value: 选项的value属性值
        """
        # 等待元素可交互
        element = self.wait.until(EC.element_to_be_clickable(locator))
        # 初始化Select对象
        select = Select(element)
        select.select_by_value(value)
        self.logger.info(f"通过value '{value}' 选择下拉框选项: {locator}")